import random
import datetime
import json


def get_vin(number: int) -> list:
    """
    用来生成指定数量的车架号

    param:number    生成多少量车的数据
    return: 返回一个列表
    """
    vin_set = set()  # 创建一个空集合
    for i in range(number):
        vin = ''.join(random.choices(
            '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', k=17))  # 车架号
        if vin not in vin_set:
            vin_set.add(vin)
    return list(vin_set)


class GenerationData:
    def __init__(self, vin):
        self.vin = vin
        # 随机生成初始化里程：
        self.mileage = round(random.uniform(200, 50000), 2)  # 行驶总里程
        # 随机初始化电量
        self.soc = round(random.uniform(10, 100), 2)  # 剩余电量SOC
        # 需要混入少量前几天的数据（让其在今天和前几天中选择，但被选中的几率极低）
        self.now_day = random.choices([(datetime.datetime.now() - datetime.timedelta(days=random.randint(1, 3))),
                                      datetime.datetime.now()], weights=[0.1, 0.9])[0]
        # 随机选取司机用车时间(或充电的时间)
        self.use_car_time = \
            random.choices([self.get_random_date(0, 23), self.get_random_date(8, 11), self.get_random_date(16, 22)],
                           weights=[0.2, 0.4, 0.4])[0]  # 模拟这个时间段用车的人较多
        # 低点预警
        self.soc_low_alarm = '电量正常' if self.soc >= 30 else '电量低于30%'
        self.time_compare = datetime.datetime.combine(self.now_day.date(), datetime.time(23, 59, 50))

    @staticmethod
    def get_random_speed():
        """
        用来随机获取车速
        """
        speed1 = random.randint(0, 60)
        speed2 = random.randint(61, 90)
        speed3 = random.randint(91, 120)
        speed = random.choices([speed1, speed2, speed3], weights=[0.6, 0.3, 0.1])[0]

        return speed

    def get_random_date(self, start: int, end: int):
        """
        用来生成当天的随机时间

        param:start 起始时间(小时)
        param:end   结束时间(小时)
        return:     当天的随机时间
        """

        # 生成随机的小时数、分钟数和秒数，并构造时间对象
        random_time = datetime.time(random.randint(
            start, end), random.randint(0, 59), random.randint(0, 59))

        # 构造完整的时间对象，包含日期和时间
        random_datetime = datetime.datetime.combine(self.now_day.date(), random_time)
        return random_datetime

    @staticmethod
    def get_travel_time():
        """
            随机获取一个随机时间(利用随机权重来设置)
        """
        random_hour_list = [0, 1, 2, random.randint(3, 5), random.randint(5, 8), random.randint(8, 10),
                            random.randint(10, 15), random.randint(15, 23)]
        random_hour = random.choices(random_hour_list, weights=[
            0.7, 0.3, 0.2, 0.15, 0.1, 0.05, 0.03, 0.01])[0]
        random_time = datetime.time(
            random_hour, random.randint(0, 59), random.randint(0, 59))
        return random_time

    def if_not_charge(self, choose_charge_or_not):
        data_list = []
        steer_time = self.get_travel_time()  # 随机获取一个驾驶时间
        print("司机本次开车", steer_time)
        seconds = (steer_time.hour * 60 + steer_time.minute) * 60 + steer_time.second  # 将驾驶时间转换为秒数
        flag = 0  # 立一个旗帜来判断是否达到驾驶时间
        print("司机的用车时间为:", self.use_car_time)

        while flag <= seconds:
            speed = self.get_random_speed()  # 随机生成0-130的车速,但侧重于0-60的车速
            if speed == 0:  # 模拟行驶中可能会停住
                status = '停车中'  # 设置车辆状态
                for i in range(random.randint(0, 50)):  # 模拟停车
                    self.use_car_time += datetime.timedelta(seconds=2)  # 每两秒更新一次时间
                    if self.use_car_time > self.time_compare:  # 保证生成的是今天的数据
                        break
                    data = {
                        '车架号': self.vin,
                        '行驶总里程': self.mileage,
                        '车速': speed,
                        '车辆状态': status,
                        '充电状态': choose_charge_or_not,
                        '剩余电量SOC': self.soc,
                        'SOC低报警': self.soc_low_alarm,
                        '数据生成时间': self.use_car_time.strftime('%Y-%m-%d %H:%M:%S')
                    }
                    data_list.append(data)
                    flag += 2
            else:
                status = '行驶中'
                # 假设每两秒随机跑10-60米
                run_mileage = random.uniform(0.01, 0.06)  # 因为速度是随机生成的,计算起来会不符合逻辑,那么这两秒跑了远也随机吧
                self.mileage += run_mileage  # 更新行驶里程
                consume = run_mileage * 0.1  # 消耗电量
                self.soc -= consume  # 更新剩余电量
                if self.soc <= 10:  # 电量小于10停止行驶
                    break
                self.soc_low_alarm = '电量正常' if self.soc > 30 else '电量低于30%'  # 低电提示
                # 加上时间差2秒
                self.use_car_time += datetime.timedelta(seconds=2)

                if self.use_car_time > self.time_compare:  # 保证生成的是今天的数据
                    break
                data = {
                    '车架号': self.vin,
                    '行驶总里程': self.mileage,
                    '车速': speed,
                    '车辆状态': status,
                    '充电状态': choose_charge_or_not,
                    '剩余电量SOC': self.soc,
                    'SOC低报警': self.soc_low_alarm,
                    '数据生成时间': self.use_car_time.strftime('%Y-%m-%d %H:%M:%S')
                }
                data_list.append(data)
                flag += 2
        print("本次用完车后的时间:", self.use_car_time)
        return data_list

    def if_charge(self, choose_charge_or_not):
        data_list = []
        status = '停车中'  # 默认停车中
        speed = 0  # 车速=0
        # 这时候就要考虑车主要充多电多久(随机生成充电时间)
        charge_timme = self.get_travel_time()
        print("司机本次充电时间", charge_timme)
        seconds = (charge_timme.hour * 60 + charge_timme.minute) * 60 + charge_timme.second  # 将充电时间转换为秒数
        flag = 0
        print("司机开始充电时间为:", self.use_car_time)
        print("未充电前电量为:", self.soc)
        if self.soc < 90:  # 如果此时车的电量是大于90的，那么就没有充电的必要了
            while flag <= seconds:
                if self.soc < 100:  # 确保电量不会超过100
                    self.soc_low_alarm = '电量正常' if self.soc > 30 else '电量低于30%'  # 低电提示
                    self.soc += 2 * (1 / 60)  # 充电效率跟充电桩有一定因素，暂不考虑太多，就假设1分钟充1%的电
                    self.use_car_time += datetime.timedelta(seconds=2)  # 每两秒更新一次时间
                    if self.use_car_time > self.time_compare:  # 保证生成的是今天的数据
                        break
                    data = {
                        '车架号': self.vin,
                        '行驶总里程': self.mileage,
                        '车速': speed,
                        '车辆状态': status,
                        '充电状态': choose_charge_or_not,
                        '剩余电量SOC': self.soc,
                        'SOC低报警': self.soc_low_alarm,
                        '数据生成时间': self.use_car_time.strftime('%Y-%m-%d %H:%M:%S')
                    }
                    data_list.append(data)
                    flag += 2
                else:
                    break
            print("司机结束充电时间为:", self.use_car_time)
            print("充电结束后电量为:", self.soc)
        return data_list

    def choose_whether_charge(self):
        charging = ['未充电', '充电中']
        if self.soc < 30:
            choose_charge_or_not = random.choices(charging, weights=[0.2, 0.8])[0]  # 如果电量小于30，则选择充电的就有80%的几率选择充电
        else:
            choose_charge_or_not = random.choices(charging, weights=[0.8, 0.2])[0]  # 反之就有80%选择不充电

        return choose_charge_or_not

    def generation_data(self):
        data_list = []
        # 选择是充电
        choose_charge_or_not = self.choose_whether_charge()

        while True:
            if self.use_car_time > self.time_compare:  # 保证生成的是今天的数据
                break
            if choose_charge_or_not == '未充电':
                list1 = self.if_not_charge(choose_charge_or_not)
                data_list = data_list + list1  # 将列表合并
                # 退出循环即熄火，熄火之后又面临后续是否还需要用车
                # 有三种可能:在随机时间后用车，在随机时间后充电，今天不在用车
                if self.soc < 30:
                    weights = [0.1, 0.5, 0.4]
                else:
                    weights = [0.5, 0.1, 0.4]
                choose_may = random.choices(['随机时间后用车', '随机时间后充电', '今天不在用车'], weights=weights)[0]
                if choose_may == '随机时间后用车':  # 随机时间后用车，就将司机上次用完车后的时间+上随机时间
                    random_time = self.get_travel_time()
                    print(random_time, "后用车")
                    seconds = (random_time.hour * 60 + random_time.minute) * 60 + random_time.second
                    self.use_car_time += datetime.timedelta(seconds=seconds)  # 更新下次用车时间

                    continue  # 结束本次循环，等待下次用车
                elif choose_may == '随机时间后充电':
                    choose_charge_or_not = '充电中'  # 修改充电状态
                    random_time = random.randint(10, 500)
                    print(random_time, "后用充电")
                    self.use_car_time += datetime.timedelta(seconds=random_time)  # 更新下次用车时间
                    if self.use_car_time > self.time_compare:  # 保证生成的是今天的数据
                        break
                    continue  # 结束本次循环，等待下次用车
                else:
                    print("今天不再用车")
                    break
            else:  # 这种情况：车在充电
                list2 = self.if_charge(choose_charge_or_not)
                data_list = data_list + list2  # 将列表合并
                # 充好电后，之后又面临后续是否还需要用车
                # 有两种可能：随机时间后用车，今天不在用车
                choose_may = random.choices(['随机时间后用车', '今天不在用车'], weights=[0.7, 0.3])[0]
                if choose_may == '随机时间后用车':  # 随机时间后用车，就将司机上次用完车后的时间+上随机时间
                    choose_charge_or_not = '未充电'  # 修改充电状态
                    random_time = self.get_travel_time()
                    print(random_time, "后用车")
                    seconds = (random_time.hour * 60 + random_time.minute) * 60 + random_time.second
                    self.use_car_time += datetime.timedelta(seconds=seconds)  # 更新下次用车时间
                    if self.use_car_time > self.time_compare:  # 保证生成的是今天的数据
                        break
                    continue  # 结束本次循环，等待下次用车
                else:
                    print("今天不在用车")
                    break
        return data_list


if __name__ == '__main__':
    vins_list = get_vin(50)  # 生成50辆车的数据
    vin1 = vins_list.pop()  # 随机取出一辆，先模拟一辆车
    gd = GenerationData(vin1)

    data_li = gd.generation_data()
    json.dumps(data_li)
    with open("data.json", "w", encoding="utf-8") as f:
        json.dump(data_li, f, ensure_ascii=False, )
